﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml;


namespace FRL.DebtChase.Common
{

	/// <summary>
	/// Static class of extensions
	/// </summary>
	public static class Extensions
	{

		/////////////////////////////////////////////////////////////////
		// Constants

		public const Int64 BytesInKb = 1024;
		public const Int64 BytesInMb = 1024 * 1024;
		public const Int64 BytesInGb = 1024 * 1024 * 1024;

		/////////////////////////////////////////////////////////////////
		// Attributes

		private static XmlWriterSettings m_xmlWriterSettings;
		private static XmlReaderSettings m_xmlReaderSettings;

		private static XmlWriterSettings m_xmlInternalWriterSettings;

		private static Random m_rnd = new Random();

		/////////////////////////////////////////////////////////////////
		// Helper functions

		public static double DoubleDiv(Int64 lA, Int64 lB)
		{
			return ((double)lA) / ((double)lB);
		}

		/////////////////////////////////////////////////////////////////
		// Extension - Compare two buffers

		public static bool Compare(this byte[] Array1, byte[] Array2)
		{

			// Shortcuts
			if (Array2 == null)
				return false;
			if (Array1.Length != Array2.Length)
				return false;

			// Loop through array
			for (int i1 = 0; i1 < Array1.Length; i1++)
			{
				if (Array1[i1] != Array2[i1])
					return false;
			}

			// Return success
			return true;

		}

		/////////////////////////////////////////////////////////////////
		// Extension - Round up to pence

		public static double RoundUpPence(this double dbl1)
		{

			// Round the figure to the nearest hundredths
			return Math.Round(dbl1 * 100) / 100.0;

		}

		public static decimal RoundUpPence(this decimal dc1)
		{

			// Round the figure to the nearest hundredths
			double dbl1 = (double)dc1;
			return (decimal) dbl1.RoundUpPence();

		}

		/////////////////////////////////////////////////////////////////
		// Extension - Convert exception to string

		public static string ToDetailsString(this Exception x)
		{

			// Build string
			StringBuilder sb = new StringBuilder();
			sb.Append(x.Message);
			if (x.InnerException != null)
				sb.AppendFormat(" INNER EXCEPTION: {0}", x.InnerException);

			// Return variable
			return sb.ToString();

		}

		/////////////////////////////////////////////////////////////////
		// Extension - Get values in list A that are not in list B

		public static List<int> Minus(this List<int> listA, List<int> listB)
		{

			// Build a return list
			List<int> Ftmp = new List<int>();

			// Loop through list A
			foreach (int iA in listA)
			{
				if (!listB.Contains(iA))
					Ftmp.Add(iA);
			}

			// Return list
			return Ftmp;

		}

		/////////////////////////////////////////////////////////////////
		// Convert string to a string that can be inserted into an SQL statement

		public static string ToDBString(this string strIn)
		{

			// declare return variable
			String strFtmp;

			// if not an empty string...
			if ((strIn == null) || (strIn.Length == 0))
			{

				// use the null keyword
				strFtmp = "NULL";

			}
			else
			{

				// double-up the inverted commas
				strFtmp = String.Format("'{0}'", strIn.Replace("'", "''"));

			}

			// return variable
			return strFtmp;

		}

		/////////////////////////////////////////////////////////////////
		// Convert time in minutes to 00:00 format

		public static string ToTimeMinutesString(this int iTimeMinutes)
		{

			// Declare return variable
			string strFtmp = null;

			// Declaration of local variables
			int iTemp = iTimeMinutes % 60;

			// Build a string
			StringBuilder sb = new StringBuilder();

			// Format
			if (iTimeMinutes == 0)
				strFtmp = "Midnight";
			else if (iTimeMinutes == 720)
				strFtmp = "Midday";
			else if (iTimeMinutes < 720)
				strFtmp = string.Format("{0}:{1}AM", iTimeMinutes / 60, iTemp.ToString().PadLeft(2,'0'));
			else
				strFtmp = string.Format("{0}:{1}PM", (iTimeMinutes - 12) / 60, iTemp.ToString().PadLeft(2, '0'));

			// Return variable
			return strFtmp;

		}

		/////////////////////////////////////////////////////////////////
		// String comparison

		public static int CompareWithoutUnicodePrefix(this string strA, string strB, bool bIgnoreCase)
		{

			// Prepare strings
			string strACopy;
			if ( (!string.IsNullOrEmpty(strA)) && (strA.Length > 1) && (strA[0] == 0xFEFF))
				strACopy = strA.Substring(1);
			else
				strACopy = strA;
			string strBCopy;
			if ((!string.IsNullOrEmpty(strB)) && (strB.Length > 1) && (strB[0] == 0xFEFF))
				strBCopy = strB.Substring(1);
			else
				strBCopy = strB;

			// Compare strings
#if SILVERLIGHT
			return string.Compare(
				strACopy, 
				strBCopy, 
				bIgnoreCase ? StringComparison.CurrentCultureIgnoreCase : StringComparison.CurrentCulture
			);
#else
			return string.Compare(
				strACopy, 
				strBCopy, 
				bIgnoreCase
			);
#endif // #if SILVERLIGHT

		}

		/////////////////////////////////////////////////////////////////
		// Format date into sql string

#if !SILVERLIGHT
		public static String ToDBString(this DateTime dateIn)
		{

			// declare return variable
			String strFtmp = "NULL";

			// if not null...
			if (dateIn.ToBinary() != 0)
			{

				// format date
				strFtmp = String.Format("'{0}/{1}/{2} {3}:{4}:{5}'",
						dateIn.Month,
						dateIn.Day,
						dateIn.Year,
						dateIn.Hour,
						dateIn.Minute,
						dateIn.Second
					);

			}

			// return variable
			return strFtmp;

		}
#endif // #if !SILVERLIGHT

		/////////////////////////////////////////////////////////////////
		// Do the two lists match

		public static bool Match(this List<int> listA, List<int> listB )
		{

			// Shortcuts 
			if (listA.Count != listB.Count)
				return false;
			if (listA.Count == 0)
				return true;

			// Compare content
			for (int i1 = 0; i1 < listA.Count; i1++)
			{
				bool bFound = false;
				for (int i2 = 0; i2 < listB.Count; i2++)
				{
					if (listA[i1] == listB[i2])
					{
						bFound = true;
						break;
					}
				}
				if (!bFound)
					return false;
			}

			// return SUCCESS
			return true;

		}

		/////////////////////////////////////////////////////////////////
		// Add value to list if not zero and not already in list

		public static void AddUniqueNonZeroValue( this List<int> list, int iValue )
		{

			// if not zero...
			if (iValue != 0)
			{

				// If not already in list
				if (!list.Contains(iValue))
				{

					// Add to list
					list.Add(iValue);

				}

			}

		}

		/////////////////////////////////////////////////////////////////
		// Format numeric with commas

		public static String FormatWithCommas(this Int64 l1)
		{

			// shortcuts
			if (l1 == 0)
				return "0";

			// format number
			return l1.ToString("#,#");

		}

		/////////////////////////////////////////////////////////////////
		// Covert file size to string

		public static String FriendlyFilesize(Int64 lFilesize)
		{

			// declaration of local variables
			double dbl1 = 0;

			// declare return variable
			String strFtmp = "";

			if (lFilesize == 0)
				strFtmp = "0 bytes";
			if (lFilesize < BytesInMb)
			{
				dbl1 = DoubleDiv(lFilesize,BytesInKb);
				if (dbl1 < 10)
					strFtmp = String.Format("{0:0.00}Kb", dbl1);
				else if (dbl1 < 100)
					strFtmp = String.Format("{0:0.0}Kb", dbl1);
				else
					strFtmp = String.Format("{0}Kb", FormatWithCommas(Convert.ToInt32(dbl1)));
			}
			else if (lFilesize < BytesInGb)
			{
				dbl1 = DoubleDiv(lFilesize,BytesInMb);
				if (dbl1 < 10)
					strFtmp = String.Format("{0:0.00}Mb", dbl1);
				else if (dbl1 < 100)
					strFtmp = String.Format("{0:0.0}Mb", dbl1);
				else
					strFtmp = String.Format("{0}Mb", FormatWithCommas(Convert.ToInt32(dbl1)));
			}
			else
			{
				dbl1 = DoubleDiv(lFilesize,BytesInGb);
				if (dbl1 < 10)
					strFtmp = String.Format("{0:0.00}Gb", dbl1);
				else if (dbl1 < 100)
					strFtmp = String.Format("{0:0.0}Gb", dbl1);
				else
					strFtmp = String.Format("{0}Gb", FormatWithCommas(Convert.ToInt32(dbl1)));
			}

			// return variable
			return strFtmp;

		}

		/////////////////////////////////////////////////////////////////
		// Compose a copy name, for example "Copy of Workflow"

		public static string ComposeCopyName(this string strIn, List<string> strExistingNames)
		{

			// Return variable
			string strFtmp;

			// Get the suffix
			string strSuffix;
			if (strIn.StartsWith("Copy of ", StringComparison.CurrentCultureIgnoreCase))
				strSuffix = strIn.Substring(8);
			else if (strIn.StartsWith("Copy(", StringComparison.CurrentCultureIgnoreCase))
			{
				int iPos = strIn.IndexOf(") of ");
				if(iPos!=-1)
					strSuffix = strIn.Substring(iPos + 5);
				else
					strSuffix = strIn;
			}
			else
				strSuffix = strIn;

			// Loop through counter
			int iCounter = 1;
			while (true)
			{

				// Build a new name
				if (iCounter == 1)
					strFtmp = string.Format("Copy of {0}", strSuffix);
				else
					strFtmp = string.Format("Copy({0}) of {1}", iCounter, strSuffix);

				// If name is unique...
				if (!strExistingNames.Contains(strFtmp))
					break;

				// Move on
				iCounter++;

			} // while-loop

			// Return variable
			return strFtmp;

		}

		/////////////////////////////////////////////////////////////////
		// Add ellipses to long strings

		public static string DotDotDot(this string strIn, int iMaxChars)
		{

			// If the string is short enough...
			if (string.IsNullOrEmpty(strIn))
				return strIn;
			else if (strIn.Length <= iMaxChars)
				return strIn;
			else
				return string.Format("{0}...", strIn.Substring(0, iMaxChars - 3));

		}

		/////////////////////////////////////////////////////////////////
		// Convert date-time to cute string

		public static string ToCuteString(this DateTime dt)
		{

			// Declare return variable
			string strFtmp;

			// Compare spans
			TimeSpan span = DateTime.Now - dt;
			if (span.TotalMinutes <= 5)
				strFtmp = "< 5 mins";
			else if (span.TotalDays <= 1)
				strFtmp = string.Format("Today {0}", dt.ToShortTimeString());
			else if(span.TotalDays <= 2)
				strFtmp = string.Format("Yesterday {0}", dt.ToShortTimeString());
			else
				strFtmp = string.Format("{0:d}", dt);

			// Return variable
			return strFtmp;

		}

		/////////////////////////////////////////////////////////////////
		// Convert date-time to cute span string

		public static string ToCuteSpanString(this DateTime dt)
		{

			// Declare return variable
			string strFtmp;

			// Compare spans
			TimeSpan span = DateTime.Now - dt;
			if (span.TotalMinutes <= 5)
				strFtmp = "< 5 mins";
			else if (span.TotalHours < 1)
				strFtmp = string.Format("{0} mins", Math.Floor(span.TotalMinutes));
			else if (span.TotalDays < 1)
				strFtmp = string.Format("{0} hours", Math.Floor(span.TotalHours));
			else
				strFtmp = string.Format("{0} days", Math.Floor(span.TotalDays));

			// Return variable
			return strFtmp;

		}

		/////////////////////////////////////////////////////////////////
		// Extension for reading integer

		public static int SafeReadIntAttribute(this XmlReader r, string strAttr, int iDefault)
		{

			// declare return variable
			int iFtmp = iDefault;

			// read string value
			string strValue = r.GetAttribute(strAttr);
			if (strValue != null)
			{

				// convert to numeric
				int iValue = 0;
				if (Int32.TryParse(strValue, out iValue))
					iFtmp = iValue;

			}

			// return variable
			return iFtmp;

		}

		/////////////////////////////////////////////////////////////////
		// Extensions for reading and writing XML

		public static void SafeWriteDateTime(this XmlWriter w, string strElementName, DateTime date, bool bIncludeTime)
		{

			// Write element
			w.WriteStartElement(strElementName);
			{

				// Write attributes
				w.WriteAttributeString("year", date.Year.ToString());
				w.WriteAttributeString("month", date.Month.ToString());
				w.WriteAttributeString("day", date.Day.ToString());
				if (bIncludeTime)
				{
					w.WriteAttributeString("hour", date.Hour.ToString());
					w.WriteAttributeString("minute", date.Minute.ToString());
					w.WriteAttributeString("second", date.Second.ToString());
				}

			}
			w.WriteEndElement();

		}

		public static DateTime SafeReadDateTime(this XmlReader r, string strElementName)
		{

			// Declare return variable
			DateTime Ftmp;

			// Read attributes
			int iYear = r.SafeReadIntAttribute("year",0);
			int iMonth = r.SafeReadIntAttribute("month",0);
			int iDay = r.SafeReadIntAttribute("day",0);
			if(r.GetAttribute("hour") == null)
				Ftmp = new DateTime(iYear,iMonth,iDay);
			else
			{
				int iHour = r.SafeReadIntAttribute("hour", 0);
				int iMinute = r.SafeReadIntAttribute("minute", 0);
				int iSecond = r.SafeReadIntAttribute("second", 0);
				Ftmp = new DateTime(iYear,iMonth,iDay,iHour,iMinute,iSecond);
			}

			// Return variable
			return Ftmp;

		}

		public static double SafeReadDoubleAttribute(this XmlReader r, string strAttr, double dblDefault)
		{

			// declare return variable
			double dblFtmp = dblDefault;

			// read string value
			string strValue = r.GetAttribute(strAttr);
			if (strValue != null)
			{

				// convert to numeric
				double dblValue = 0;
				if (double.TryParse(strValue, out dblValue))
					dblFtmp = dblValue;

			}

			// return variable
			return dblFtmp;

		}

		public static void WriteAttributeBoolean( this XmlWriter w, string strAttr, bool b1 )
		{

			// Write boolean value
			w.WriteAttributeString(strAttr, b1 ? "True" : "False");

		}

		public static void SafeWriteAttributeString(this XmlWriter w, string strName, string strAttr)
		{

			// If the string is not empty...
			if (!string.IsNullOrEmpty(strAttr))
			{
				w.WriteAttributeString(strName, strAttr.TrimEnd());
			}

		}

		public static bool SafeReadBooleanAttribute(this XmlReader r, string strAttr, bool bDefault)
		{

			// declare return variable
			bool bFtmp = bDefault;

			// read string value
			string strValue = r.GetAttribute(strAttr);
			if (strValue != null)
			{

				// convert to boolean
				if (string.Compare(strValue, "true", StringComparison.CurrentCultureIgnoreCase) == 0)
					bFtmp = true;
				else
					bFtmp = false;

			}

			// return variable
			return bFtmp;

		}

		public static Guid SafeReadGuidAttribute(this XmlReader r, string strAttr)
		{

			// Declare return variable
			Guid Ftmp = Guid.Empty;

			// read string value
			string strValue = r.GetAttribute(strAttr);
			if (strValue != null)
			{

				// Parse string to guid
				if (!Guid.TryParse(strValue, out Ftmp))
				{
				}

			}

			// Return variable
			return Ftmp;

		}

		public static bool SkipXmlElement(this XmlReader r)
		{

			// shortcuts
			if (r.IsEmptyElement)
				return true;

			// declare return variable
			bool bFtmp = true;

			// retain element name
			String strName = r.Name;

			// loop through elements
			Int32 iLevel = 0;
			while (true)
			{

				// read next node
				if (!r.Read())
				{
					bFtmp = false;
					break;
				}

				// check for end of element
				if ((iLevel == 0) && (r.Name == strName) && (r.NodeType == XmlNodeType.EndElement))
					break;

				// update level
				if (r.NodeType == XmlNodeType.Element)
				{
					if (!r.IsEmptyElement)
						iLevel++;
				}
				else if (r.NodeType == XmlNodeType.EndElement)
					iLevel--;

			} // while-loop

			// return variable
			return bFtmp;

		}

		public static XmlWriterSettings GetXmlWriterSettings()
		{

			// If settings have not been created yet...
			if (m_xmlWriterSettings == null)
			{

				// Creating XmlWriter Settings
				m_xmlWriterSettings = new XmlWriterSettings();
				m_xmlWriterSettings.Indent = true;
				m_xmlWriterSettings.IndentChars = "  ";
				m_xmlWriterSettings.Encoding = Encoding.Unicode;
				m_xmlWriterSettings.NewLineOnAttributes = false;

			}

			// Return reference
			return m_xmlWriterSettings;

		}

		public static XmlWriterSettings GetXmlInternalWriterSettings()
		{

			// If settings have not been created yet...
			if (m_xmlInternalWriterSettings == null)
			{

				// Creating XmlWriter Settings
				m_xmlInternalWriterSettings = new XmlWriterSettings();
				m_xmlInternalWriterSettings.Indent = false;
				m_xmlInternalWriterSettings.OmitXmlDeclaration = true;
				m_xmlInternalWriterSettings.Encoding = Encoding.Unicode;
				m_xmlInternalWriterSettings.NewLineOnAttributes = false;

			}

			// Return reference
			return m_xmlInternalWriterSettings;

		}

		public static XmlReaderSettings GetXmlReaderSettings()
		{

			// If settings have not been cerated yet...
			if (m_xmlReaderSettings == null)
			{

				// Creating XmlWriter Settings
				m_xmlReaderSettings = new XmlReaderSettings();
				m_xmlReaderSettings.IgnoreWhitespace = true;

			}

			// Return reference
			return m_xmlReaderSettings;

		}

		/////////////////////////////////////////////////////////////
		// Extension for formatting a list into a comma delimited string with an "and" preeding the last item
		// For example "apples, oranges and bananas"

		public static string ToAndList(this List<string> list)
		{

			// Shortcuts
			if (list.Count == 0)
				return string.Empty;
			else if (list.Count == 1)
				return list[0];

			// Build a string
			StringBuilder sb = new StringBuilder();
			for (int i1 = 0; i1 < list.Count; i1++)
			{
				if ((i1 > 0) && (i1 < (list.Count - 1)))
					sb.Append(", ");
				else if ((i1 == (list.Count - 1)))
					sb.Append(" and ");
				sb.Append(list[i1]);
			}

			// Return string
			return sb.ToString();

		}

		/////////////////////////////////////////////////////////////
		// Format a list of strings into a comma-delimited list

		public static string ToPackedCommaList(this List<string> list)
		{

			// Shortcuts
			if (list.Count == 0)
				return string.Empty;
			else if (list.Count == 1)
				return list[0];

			// Build a string
			StringBuilder sb = new StringBuilder();
			for (int i1 = 0; i1 < list.Count; i1++)
			{
				if (i1 > 0)
					sb.Append(",");
				sb.Append(list[i1]);
			}

			// Return string
			return sb.ToString();

		}

		/////////////////////////////////////////////////////////////
		// Unpack string from a list of strings into a comma-delimited list

		public static List<string> FromPackedCommaList(this string strIn)
		{

			// Declare return variable
			List<string> Ftmp = new List<string>();

			// Shortuts
			if (!string.IsNullOrEmpty(strIn))
			{

				// Split the string
				string[] strArray = strIn.Split(Definitions.CommaAsCharArray, StringSplitOptions.RemoveEmptyEntries);
				for (int i1 = 0; i1 < strArray.Length; i1++)
					Ftmp.Add(strArray[i1].Trim());

			}

			// Return string
			return Ftmp;

		}

		/////////////////////////////////////////////////////////////
		// Extension for joining a list of strings

		public static string Join( this List<string> list, string strJoin )
		{

			// Shortcuts
			if (list.Count == 0)
				return string.Empty;
			else if (list.Count == 1)
				return list[0];

			// Build a string
			StringBuilder sb = new StringBuilder();
			for (int i1 = 0; i1 < list.Count; i1++)
			{
				if (i1 > 0)
					sb.Append(strJoin);
				sb.Append(list[i1]);
			}

			// Return string
			return sb.ToString();

		}

		/////////////////////////////////////////////////////////////
		// Get the Silverlight version in a string

		public static string GetVersion(this Assembly assembly)
		{

			// Split the version from the product name
			string strVersion = assembly.FullName.Split(',')[1];
			strVersion = strVersion.Substring(strVersion.IndexOf('=')+1);

			// Split the version into components
			string[] strParts = strVersion.Split('.');

			// Build a version string
			return string.Format("ver {0}.{1}.{2}", strParts[0], strParts[1], strParts[2]);

		}

		/////////////////////////////////////////////////////////////////
		// Create a password

		public static string MakePassword( int iLength )
		{

			// Generate random password
			StringBuilder sb = new StringBuilder();
			for (int i1 = 0; i1 < iLength; i1++)
			{

				// Get next character
				int iRnd = m_rnd.Next(0, 10 + 26 + 26);
				if (iRnd < 10)
					sb.Append(iRnd.ToString());
				else if (iRnd < 36)
					sb.Append(Convert.ToChar(iRnd - 10 + 'a'));
				else
					sb.Append(Convert.ToChar(iRnd - 36 + 'A'));

			}

			// Return variable
			return sb.ToString();

		}

		/////////////////////////////////////////////////////////////////
		// Add string if not already in list

		public static void SafeAddString(ref List<string> strList, string strValue)
		{

			// Add string to a list, create the list if it does not exist
			if (strList == null)
				strList = new List<string>();
			if (!strList.Contains(strValue))
				strList.Add(strValue);

		}

		/////////////////////////////////////////////////////////////////
		// Create a random string

		public static string MakeRandomString(string strFormat)
		{

			// Declaration of local variables
			int iTemp;

			// Loop through format
			StringBuilder sb = new StringBuilder();
			for( int i1=0; i1<strFormat.Length; i1++ )
			{

				// Compare formats
				if(strFormat[i1] == 'X')
				{

					// Pick character
					iTemp = m_rnd.Next(36);
					if (iTemp < 10)
						sb.Append(Convert.ToChar('0' + iTemp));
					else
						sb.Append(Convert.ToChar('A' + iTemp - 10));

				}
				else if(strFormat[i1] == 'A')
				{

					// Pick character
					iTemp = m_rnd.Next(26);
					sb.Append(Convert.ToChar('A' + iTemp - 10));

				}
				else if(strFormat[i1] == '9')
				{

					// Pick character
					iTemp = m_rnd.Next(10);
					sb.Append(Convert.ToChar('0' + iTemp));

				}

			}

			// Return string
			return sb.ToString();

		}

		/////////////////////////////////////////////////////////////////
		// Convert number to short genetive

		public static string ToShortGenetive(this int iValue)
		{

			// Compare strings
			if (
				((iValue % 10) == 1)
				&&
				(iValue != 11)
			)
				return string.Format("{0}st", iValue);
			else if (
				((iValue % 10) == 2)
				&&
				(iValue != 12)
			)
				return string.Format("{0}nd", iValue);
			else if (
				((iValue % 10) == 3)
				&&
				(iValue != 13)
			)
				return string.Format("{0}rd", iValue);
			else
				return string.Format("{0}th", iValue);

		}

		/////////////////////////////////////////////////////////////////
		// Populate a dictionary from an enum

#if !SILVERLIGHT
		public static void FillWithEnum( this Dictionary<int, string> map, Type enumeration, bool bAddNoneOption, int iIgnore )
		{

			// If adding none option
			if (bAddNoneOption)
				map.Add(0, Definitions.None);

			// Add enum values to dictionary
			string[] names = Enum.GetNames(enumeration);
			Array values = Enum.GetValues(enumeration);
			for (int i1 = 0; i1 < names.Length; i1++)
			{

				// Get the value
				int iValue = (int)values.GetValue(i1);
				if (iIgnore == iValue)
					continue;

				// Add to dictionary
				map.Add(iValue, names[i1]);

			}

		}
#endif // #if !SILVERLIGHT

	}

}
